import java.io.*;
import java.net.*;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.regex.Pattern;

public class FixedClipboardSync {
    private static final String END_MARKER = "|||END|||";
    private static final int DEFAULT_PORT = 12345;
    private static final long CHECK_INTERVAL = 1000; // 检测间隔1秒
    private static String lastContent = "";
    private static boolean running = true;
    private static String osName;
    
    // 存储所有连接的客户端，增加唯一标识
    private static final List<ClientHandler> clients = new CopyOnWriteArrayList<>();
    
    // 常见文件扩展名（用于识别文件路径）
    private static final Set<String> FILE_EXTENSIONS = new HashSet<>();
    // 文件路径模式

    // 匹配文件路径的正则表达式
    private static final Pattern PATH_PATTERN = Pattern.compile(
        "(^/[a-zA-Z0-9_/-]+(\\.[a-zA-Z0-9]+)?$)|" + 
        "(^[A-Za-z]:\\\\[a-zA-Z0-9_\\\\-]+(\\.[a-zA-Z0-9]+)?$)|" + 
        "(^\\\\\\\\[a-zA-Z0-9_\\\\-]+(\\.[a-zA-Z0-9]+)?$)"
    );
    
    static {
        String[] exts = {
            "zip", "rar", "7z", "tar", "gz", "bz2",
            "jpg", "png", "gif", "bmp", "svg",
            "mp3", "mp4", "avi", "mov", "flv",
            "doc", "docx", "pdf", "xls", "xlsx",
            "bin", "exe", "iso", "img"
        };
        for (String ext : exts) {
            FILE_EXTENSIONS.add(ext.toLowerCase());
        }
    }

    public static void main(String[] args) {
        osName = System.getProperty("os.name").toLowerCase();
        System.out.println("===== 修复版剪切板同步工具 =====");
        System.out.println("解决服务端同步和循环bug");
        System.out.println("使用Ctrl+C退出程序\n");

        if (osName.contains("nix") || osName.contains("nux")) {
            if (!isCommandAvailable("xclip")) {
                System.out.println("错误: 未找到xclip工具，请先安装:");
                System.out.println("sudo apt-get install xclip");
                return;
            }
        }

        if (args.length < 1) {
            printUsage();
            return;
        }

        String mode = args[0].toLowerCase();
        int port = DEFAULT_PORT;

        try {
            if (mode.equals("server")) {
                if (args.length >= 2) {
                    port = Integer.parseInt(args[1]);
                }
                // 启动服务器剪切板监控线程
                Thread clipboardMonitor = new Thread(FixedClipboardSync::monitorClipboard);
                clipboardMonitor.start();
                startServer(port);
            } else if (mode.equals("client")) {
                if (args.length < 2) {
                    System.out.println("请指定服务器IP地址");
                    printUsage();
                    return;
                }
                String host = args[1];
                if (args.length >= 3) {
                    port = Integer.parseInt(args[2]);
                }
                startClient(host, port);
            } else {
                System.out.println("无效的模式，请使用 'server' 或 'client'");
                printUsage();
            }
        } catch (NumberFormatException e) {
            System.out.println("无效的端口号: " + args[1]);
        } catch (IOException e) {
            System.out.println("网络错误: " + e.getMessage());
        }
    }

    private static void printUsage() {
        System.out.println("用法:");
        System.out.println("  作为服务器运行: java FixedClipboardSync server [端口号，默认" + DEFAULT_PORT + "]");
        System.out.println("  作为客户端运行: java FixedClipboardSync client 服务器IP [端口号，默认" + DEFAULT_PORT + "]");
    }

    private static boolean isCommandAvailable(String command) {
        try {
            Process process = new ProcessBuilder(command, "--version").start();
            process.waitFor(2, java.util.concurrent.TimeUnit.SECONDS);
            return true;
        } catch (Exception e) {
            return false;
        }
    }

    // 服务器剪切板监控线程（只处理服务器本地变化）
    private static void monitorClipboard() {
        System.out.println("服务器剪切板监控已启动");
        while (running) {
            try {
                String currentContent = getClipboardTextContent();
                
                // 只处理真正的新内容，且不是刚从客户端同步来的内容
                if (!currentContent.isEmpty() && !currentContent.equals(lastContent)) {
                    Thread.sleep(100);
                    String doubleCheck = getClipboardTextContent();
                    if (currentContent.equals(doubleCheck)) {
                        System.out.println("===== 服务器本地剪切板变化 =====");
                        System.out.println(currentContent);
                        System.out.println("===================================");
                        // 保存当前内容为最后已知内容，防止循环
                        String prevContent = lastContent;
                        lastContent = currentContent;
                        // 广播给所有客户端
                        broadcast(currentContent, null, prevContent);
                    }
                }
                Thread.sleep(CHECK_INTERVAL);
            } catch (Exception e) {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException ie) {
                    Thread.currentThread().interrupt();
                }
            }
        }
    }

    private static void startServer(int port) throws IOException {
        ServerSocket serverSocket = new ServerSocket(port);
        System.out.println("服务器启动，监听端口 " + port);
        System.out.println("等待客户端连接...");

        lastContent = getClipboardTextContent();
        
        Runtime.getRuntime().addShutdownHook(new Thread(() -> {
            running = false;
            try {
                serverSocket.close();
                for (ClientHandler client : clients) {
                    client.close();
                }
            } catch (IOException e) {
                // 忽略关闭错误
            }
            System.out.println("\n服务器已退出");
        }));

        while (running) {
            try {
                Socket clientSocket = serverSocket.accept();
                System.out.println("新客户端已连接: " + clientSocket.getInetAddress() + 
                                   "，当前连接数: " + (clients.size() + 1));
                
                ClientHandler clientHandler = new ClientHandler(clientSocket);
                clients.add(clientHandler);
                new Thread(clientHandler).start();
            } catch (IOException e) {
                if (running) {
                    System.out.println("接受客户端连接出错: " + e.getMessage());
                }
            }
        }
    }

    private static void startClient(String host, int port) throws IOException {
        Socket socket = new Socket(host, port);
        System.out.println("已连接到服务器 " + host + ":" + port);

        lastContent = getClipboardTextContent();
        // 客户端唯一标识，用于防止循环
        String clientId = UUID.randomUUID().toString();

        Thread sendThread = new Thread(new Sender(socket, clientId));
        Thread receiveThread = new Thread(new Receiver(socket, clientId));

        sendThread.start();
        receiveThread.start();

        Runtime.getRuntime().addShutdownHook(new Thread(() -> {
            running = false;
            try {
                socket.close();
            } catch (IOException e) {
                // 忽略关闭错误
            }
            System.out.println("\n客户端已退出");
        }));

        try {
            sendThread.join();
            receiveThread.join();
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        } finally {
            socket.close();
        }
    }

    private static String getClipboardTextContent() {
        try {
            Process process;
            if (osName.contains("nix") || osName.contains("nux")) {
                process = new ProcessBuilder("xclip", "-selection", "clipboard", "-o", "-t", "text/plain")
                        .redirectErrorStream(true)
                        .start();
            } else if (osName.contains("mac")) {
                process = new ProcessBuilder("pbpaste")
                        .redirectErrorStream(true)
                        .start();
            } else {
                System.out.println("不支持的操作系统: " + osName);
                return "";
            }

            try (BufferedReader reader = new BufferedReader(
                    new InputStreamReader(process.getInputStream(), StandardCharsets.UTF_8))) {
                StringBuilder content = new StringBuilder();
                String line;
                boolean isFirstLine = true;
                
                while ((line = reader.readLine()) != null) {
                    if (!isFirstLine) {
                        content.append("\n");
                    } else {
                        isFirstLine = false;
                    }
                    content.append(line);
                }
                
                int exitCode = process.waitFor();
                String text = content.toString().trim();
                
                if (exitCode != 0) {
                    return "";
                }
                
                if (text.isEmpty()) {
                    return "";
                }
                
                if (isFilePath(text)) {
                    System.out.println("检测到文件路径，忽略同步: " + (text.length() > 50 ? text.substring(0, 50) + "..." : text));
                    return "";
                }
                
                return text;
            }
        } catch (Exception e) {
            return "";
        }
    }

    private static boolean isFilePath(String content) {
        if (content.contains("\n")) {
            return false;
        }
        
        if (PATH_PATTERN.matcher(content).matches()) {
            return true;
        }
        
        String lowerContent = content.toLowerCase();
        for (String ext : FILE_EXTENSIONS) {
            if (lowerContent.endsWith("." + ext) && lowerContent.length() > ext.length() + 1) {
                char prevChar = lowerContent.charAt(lowerContent.length() - ext.length() - 2);
                if ((Character.isLetterOrDigit(prevChar) || prevChar == '_' || prevChar == '-') &&
                    (lowerContent.contains("/") || lowerContent.contains("\\"))) {
                    return true;
                }
            }
        }
        
        return false;
    }

    private static void setClipboardContent(String content) {
        if (content == null || content.trim().isEmpty()) {
            return;
        }
        
        try {
            Process process;
            if (osName.contains("nix") || osName.contains("nux")) {
                process = new ProcessBuilder("xclip", "-selection", "clipboard", "-t", "text/plain")
                        .redirectErrorStream(true)
                        .start();
            } else if (osName.contains("mac")) {
                process = new ProcessBuilder("pbcopy")
                        .redirectErrorStream(true)
                        .start();
            } else {
                System.out.println("不支持的操作系统: " + osName);
                return;
            }

            try (OutputStream os = process.getOutputStream()) {
                os.write(content.getBytes(StandardCharsets.UTF_8));
                os.flush();
            }
            process.waitFor();
        } catch (Exception e) {
            // 忽略设置剪切板时的临时错误
        }
    }

    /**
     * 广播消息给所有客户端（除了发送者）
     * @param message 要广播的消息
     * @param sender 发送者（null表示服务器）
     * @param prevContent 发送前的内容，用于客户端判断是否是新内容
     */
    private static void broadcast(String message, ClientHandler sender, String prevContent) {
        if (message.isEmpty()) {
            return;
        }
        
        System.out.println("开始广播消息，目标客户端数: " + (sender == null ? clients.size() : clients.size() - 1));
        for (ClientHandler client : clients) {
            if (client != sender && client.isRunning()) {
                client.send(message, prevContent);
            }
        }
    }

    /**
     * 客户端处理器：增加标识和防循环逻辑
     */
    static class ClientHandler implements Runnable {
        private final Socket socket;
        private PrintWriter out;
        private BufferedReader in;
        private InetAddress clientAddress;
        private boolean isRunning = true;
        private String clientId; // 客户端唯一标识

        public ClientHandler(Socket socket) {
            this.socket = socket;
            this.clientAddress = socket.getInetAddress();
            this.clientId = UUID.randomUUID().toString(); // 生成唯一标识
        }

        public boolean isRunning() {
            return isRunning;
        }
        
        public String getClientId() {
            return clientId;
        }

        @Override
        public void run() {
            try {
                out = new PrintWriter(
                        new OutputStreamWriter(socket.getOutputStream(), StandardCharsets.UTF_8), true);
                in = new BufferedReader(
                        new InputStreamReader(socket.getInputStream(), StandardCharsets.UTF_8));

                StringBuilder buffer = new StringBuilder();
                char[] chars = new char[1024];
                int bytesRead;

                while (isRunning && running && (bytesRead = in.read(chars)) != -1) {
                    buffer.append(chars, 0, bytesRead);
                    String content = buffer.toString();
                    
                    int endIndex;
                    while ((endIndex = content.indexOf(END_MARKER)) != -1) {
                        // 消息格式: 客户端ID|||内容
                        String rawMessage = content.substring(0, endIndex);
                        String[] parts = rawMessage.split("\\|\\|\\|", 2);
                        
                        if (parts.length != 2) {
                            buffer.setLength(0);
                            buffer.append(content.substring(endIndex + END_MARKER.length()));
                            content = buffer.toString();
                            continue;
                        }
                        
                        String message = parts[1];
                        buffer.setLength(0);
                        buffer.append(content.substring(endIndex + END_MARKER.length()));
                        content = buffer.toString();
                        
                        // 验证消息有效性
                        if (!message.isEmpty() && !message.equals(lastContent)) {
                            System.out.println("===== 从客户端 " + clientAddress + " 收到内容 =====");
                            System.out.println(message);
                            System.out.println("===================================");
                            
                            // 1. 更新服务器自身剪切板（解决服务端无法共享客户端内容的问题）
                            String prevContent = lastContent;
                            lastContent = message;
                            setClipboardContent(message);
                            
                            // 2. 广播给其他客户端，排除发送者
                            broadcast(message, this, prevContent);
                        }
                    }
                }
            } catch (IOException e) {
                if (isRunning && running) {
                    System.out.println("与客户端 " + clientAddress + " 通信出错: " + e.getMessage());
                }
            } finally {
                close();
                clients.remove(this);
                System.out.println("客户端 " + clientAddress + " 已断开，当前连接数: " + clients.size());
            }
        }

        public void send(String message, String prevContent) {
            if (out != null && isRunning) {
                try {
                    // 发送格式: 服务器标识|||消息|||之前的内容
                    out.println("SERVER|||" + message + "|||" + prevContent + END_MARKER);
                    out.flush();
                } catch (Exception e) {
                    System.out.println("发送消息给 " + clientAddress + " 失败: " + e.getMessage());
                    close();
                }
            }
        }

        public void close() {
            isRunning = false;
            try {
                if (in != null) in.close();
                if (out != null) out.close();
                socket.close();
            } catch (IOException e) {
                System.out.println("关闭客户端连接时出错: " + e.getMessage());
            }
        }
    }

    /**
     * 客户端发送线程：增加唯一标识，防止循环
     */
    static class Sender implements Runnable {
        private final Socket socket;
        private final String clientId; // 客户端唯一标识

        Sender(Socket socket, String clientId) {
            this.socket = socket;
            this.clientId = clientId;
        }

        @Override
        public void run() {
            try (PrintWriter out = new PrintWriter(
                    new OutputStreamWriter(socket.getOutputStream(), StandardCharsets.UTF_8), true)) {

                while (running) {
                    String currentContent = getClipboardTextContent();
                    
                    if (!currentContent.isEmpty() && !currentContent.equals(lastContent)) {
                        Thread.sleep(100);
                        String doubleCheck = getClipboardTextContent();
                        if (currentContent.equals(doubleCheck)) {
                            System.out.println("===== 本地剪切板变化 =====");
                            System.out.println(currentContent);
                            System.out.println("===================================");
                            
                            lastContent = currentContent;
                            
                            // 发送格式: 客户端ID|||内容（带标识防止循环）
                            out.println(clientId + "|||" + currentContent + END_MARKER);
                            out.flush();
                        }
                    }
                    Thread.sleep(CHECK_INTERVAL);
                }
            } catch (IOException e) {
                if (running) {
                    System.out.println("发送数据出错: " + e.getMessage());
                    running = false;
                }
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
                running = false;
            }
        }
    }

    /**
     * 客户端接收线程：增加防循环逻辑
     */
    static class Receiver implements Runnable {
        private final Socket socket;
        private final String clientId; // 客户端唯一标识

        Receiver(Socket socket, String clientId) {
            this.socket = socket;
            this.clientId = clientId;
        }

        @Override
        public void run() {
            try (BufferedReader in = new BufferedReader(
                    new InputStreamReader(socket.getInputStream(), StandardCharsets.UTF_8))) {

                StringBuilder buffer = new StringBuilder();
                char[] chars = new char[1024];
                int bytesRead;

                while (running && (bytesRead = in.read(chars)) != -1) {
                    buffer.append(chars, 0, bytesRead);
                    String content = buffer.toString();
                    
                    int endIndex;
                    while ((endIndex = content.indexOf(END_MARKER)) != -1) {
                        // 消息格式: 发送者标识|||消息|||之前的内容
                        String rawMessage = content.substring(0, endIndex);
                        String[] parts = rawMessage.split("\\|\\|\\|", 3);
                        
                        if (parts.length != 3) {
                            buffer.setLength(0);
                            buffer.append(content.substring(endIndex + END_MARKER.length()));
                            content = buffer.toString();
                            continue;
                        }
                        
                        String senderId = parts[0];
                        String message = parts[1];
                        String prevContent = parts[2];
                        buffer.setLength(0);
                        buffer.append(content.substring(endIndex + END_MARKER.length()));
                        content = buffer.toString();
                        
                        // 防循环核心逻辑：
                        // 1. 不处理自己发送的内容
                        // 2. 只处理比当前内容新的内容
                        if (!message.isEmpty() && !senderId.equals(clientId) && 
                            (!message.equals(lastContent) || !lastContent.equals(prevContent))) {
                            
                            System.out.println("===== 收到服务器同步内容 =====");
                            System.out.println(message);
                            System.out.println("===================================");
                            
                            // 更新本地剪切板和最后已知内容
                            lastContent = message;
                            setClipboardContent(message);
                        }
                    }
                }
            } catch (IOException e) {
                if (running) {
                    System.out.println("接收数据出错: " + e.getMessage());
                    running = false;
                }
            }
        }
    }
}
    